﻿/**
 * PMSUtils: 整合一些常用功能用于前端功能扩展
 * Author: DavidLee
 * 为最大程度保证浏览器兼容性及代码可压缩，尽量避免ES6语法
 * */

//Object.assign available for ES6
//for ES5 use see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
if (typeof Object.assign !== 'function') {
	// Must be writable: true, enumerable: false, configurable: true
	Object.defineProperty(Object, "assign", {
		value: function assign(target, varArgs) { // .length of function is 2
			'use strict';
			if (target === null || target === undefined) {
				throw new TypeError('Cannot convert undefined or null to object');
			}

			var to = Object(target);

			for (var index = 1; index < arguments.length; index++) {
				var nextSource = arguments[index];

				if (nextSource !== null && nextSource !== undefined) {
					for (var nextKey in nextSource) {
						// Avoid bugs when hasOwnProperty is shadowed
						if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
							to[nextKey] = nextSource[nextKey];
						}
					}
				}
			}
			return to;
		},
		writable: true,
		configurable: true
	});
}
if (!String.prototype.endsWith && !String.prototype.startsWith) {
	String.prototype.endsWith = function (search, this_len) {
		if (this_len === undefined || this_len > this.length) {
			this_len = this.length;
		}
		return this.substring(this_len - search.length, this_len) === search;
	};
	Object.defineProperty(String.prototype, 'startsWith', {
		value: function (search, rawPos) {
			var pos = rawPos > 0 ? rawPos | 0 : 0;
			return this.substring(pos, pos + search.length) === search;
		}
	});
}
var PMSUtils = (function () {
	var util = {
		/************************************************************************
		 * Backend Admin Utils
		 ************************************************************************/
		//在父级页面（即当前frame页的父页面）获取主页面控件，用于在全局显示消息
		//使用方法：
		//notice = Util.notice !== null && Util.notice() !== null
		//	? Util.notice() : layui.notice;
		notice: function () {
			var notice = null;
			if (typeof parent.layui !== "undefined" && typeof parent.layui.notice !== "undefined")
				notice = parent.layui.notice;
			return notice;
		},

		//适用于iframe内关闭从layui.layer创建的弹窗
		closePopupWindow: function () {
			var layer = null,
				mainWin = window.parent;
			if (typeof mainWin.layui !== "undefined" && typeof mainWin.layui.layer !== "undefined")
				layer = mainWin.layui.layer;
			else return null;
			layer.closeAll();
		},
		//在后台主页的iframe页面通过调用此功能在主页面加载打开弹出窗口
		//layer弹层的简单封装，主要用于后台弹窗调用，简化选项配置
		// @param options: object 参照layui.layer options
		// PMSUtils.popupWindow({
		//		title: "",			//窗体头标题
		//		type: 2,			//可传入的值有：0（信息框，默认）1（页面层）2（iframe层）3（加载层）4（tips层）
		//      content: "",		//参考layer.content
		//		anim: 3,			//参考layer.anim
		//		fixtolayoutbody: true //与width/height选项不能同时出现(二选一) 当设置该选项时，弹窗将自动计算"layui-body"区域大小并填满该区域
		//      width: type: Number	//当设置fixtolayoutbody为true时无效，设置弹窗宽度,配合height使用
		//		height:type: Number //同width,当设置fixtolayoutbody为true时无效,设置弹窗高度，配合width使用
		//		end: callback function //参考layer.end 弹窗关闭销毁时触发
		//		success: callback function//参考layer.success 弹窗弹出后的成功回调方法
		//		... 更多选项
		// });
		popupWindow: function (options) {
			var layer = null,
				mainWin = window.parent;
			if (typeof mainWin.layui !== "undefined" && typeof mainWin.layui.layer !== "undefined")
				layer = mainWin.layui.layer;
			else return null;
			var defaultOpts = {
				type: 2, //iframe层
				title: "信息",
				content: "请添加要显示的内容",
				fixtolayoutbody: false,
				width: 500,
				height: 400,
				shade: [0.1, '#333'],
				anim: 3,
				shadeClose: false,
				skin: 'layer-anim-07',
				fullwindow: false,
				iframeAuto: false,
			};
			var layeroption = this.assign({}, defaultOpts, options);
			if (this.gettype(layeroption.fixtolayoutbody) !== "undefined" && layeroption.fixtolayoutbody) {
				var $layuibody = mainWin.layui.$('body').find("div.layui-body");
				if ($layuibody.length > 0) {
					var winH = $layuibody.height(),
						winW = $layuibody.width();
					layeroption.area = [winW + 'px', winH + 'px'];
					layeroption.offset = 'rb';
					layeroption.resize = false;
					layeroption.move = false;
				}
				else return null;
			}
			else {
				if ((layeroption.width && this.isString(layeroption.width) && !layeroption.width.endsWith("px"))
					|| (layeroption.width && this.isNumber(layeroption.width)))
					layeroption.width += "px";
				if ((layeroption.height && this.isString(layeroption.height) && !layeroption.height.endsWith("px"))
					|| (layeroption.height && this.isNumber(layeroption.height)))
					layeroption.height += "px";
				layeroption.area = layeroption.width && layeroption.height
					? [layeroption.width, layeroption.height]
					: 'auto';
				layeroption.offset = options.offset || 'auto';
				layeroption.resize = options.resize || false;
			}
			layeroption.success = function (layero, index) {
				if (typeof options.fullwindow !== "undefined" && options.fullwindow)
					layer.full(index);
				if (typeof options.iframeAuto !== "undefined" && options.iframeAuto)
					layer.iframeAuto(index);
				if (options.success) options.success(layero, index);
			};
			return layer.open(layeroption);
		},

		/************************************************************************
		* 类型检测类
		************************************************************************/
		//密码强度等级说明，字符包括：小写字母、大写字母、数字、特殊字符
		// or use PasswordStrength
		//@return: 
		//0---密码长度小于minlen或大于maxlen 
		//1---密码包含其中之一
		//2---密码包含其中之二
		//3---密码包含其中之三
		//4---密码包含其中之四
		//密码强度校验
		//param password 
		checkPwdStrong: function (password, minlen, maxlen) {
			/*
			 * 判断字符类型
			 */
			function CharMode(iN) {
				if (iN >= 48 && iN <= 57) //数字
					return 1;
				if (iN >= 65 && iN <= 90) //大写字母
					return 2;
				if (iN >= 97 && iN <= 122) //小写
					return 4;
				else
					return 8; //特殊字符
			}
			/*
			 *统计字符类型
			 */
			function bitTotal(num) {
				var modes = 0;
				for (i = 0; i < 4; i++) {
					if (num & 1) modes++;
					num >>>= 1;
				}
				return modes;
			}
			if (password.length < minlen || password.length > maxlen)
				return 0; //密码太短或太长
			var Modes = 0;
			for (i = 0; i < password.length; i++) {
				//测试每一个字符的类别并统计一共有多少种模式.
				Modes |= CharMode(password.charCodeAt(i));
			}
			return bitTotal(Modes);
		},

		isValidEmail: function (email) {
			if (!this.isString(email)) return false;
			if (email.length <= 0) return false;
			return /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(email);
        },

		isValidMobile: function (phone) {
			if (!this.isString(phone)) return false;
			if (phone.length < 11) return false;
			return /^1\d{10}$/.test(phone);
		},

		// 判断是否为一个数字
		isNumber: function (value) {
			return !isNaN(parseFloat(value)) && isFinite(value);
		},

		// 判断数组
		isArray: function (value) {
			return Object.prototype.toString.call(value) === "[object Array]";
		},
		// 或
		// isArray: val => !!val && Array.isArray(val),

		// 判断原生函数
		isFunction: function (value) {
			return Object.prototype.toString.call(value) === "[object Function]";
		},
		// 或
		// isFunction: val => val && typeof val === "function",

		// 判断正则表达式
		isRegExp: function (value) {
			return Object.prototype.toString.call(value) === "[object RegExp]";
		},

		// 判断字符串
		isString: function (str) {
			return Object.prototype.toString.call(str) === "[object String]";
		},
		// 或
		// isString: val => typeof val === "string",

		// 判断布尔值
		isBoolean: function (val) {
			return Object.prototype.toString.call(val) === "[object Boolean]";
		},
		// 或
		// isBoolean: val => typeof val === "boolean",

		// 判断是否为Symbol
		isSymbol: function (val) {
			return Object.prototype.toString.call(val) === "[object Symbol]";
		},
		// 或
		// isSymbol: val => typeof val === "symbol",

		isObject: function (obj) {
			//return Object.prototype.toString.call(obj) === '[object Object]';
			return (typeof obj === "object" || typeof obj === 'function') && (obj !== null);
        },
		/**
		  * 判断是否对象
		  *
		  * @param {Object} obj 对象
		  * @return {Boolean}
		  */
		isPlainObject: function (obj) {
			return obj ?obj.constructor === Object : false
		},

		/************************************************************************
		 * browser浏览器
		************************************************************************/
		// bottomVisible: 如果页的底部可见, 则返回true, 否则为false
		// 使用scrollY、scrollHeight和clientHeight来确定页面底部是否可见。
		isBottomVisible: function () {
			return document.documentElement.clientHeight + window.scrollY >= document.documentElement.scrollHeight || document.documentElement.clientHeight;
		},

		// currentURL: 返回当前 URL
		// 使用window.location.href获取当前 URL
		currentURL: function () {
			return window.location.href;
		},

		// elementIsVisibleInViewport： 如果指定的元素在视区中可见, 则返回true, 否则为false
		// 使用Element.getBoundingClientRect()和window.inner(Width|Height)值以确定给定元素在视区中是否可见。省略第二个参数以确定该元素是否完全可见, 或指定true以确定它是否部分可见
		isElementIsVisibleInViewport: function (el, partiallyVisible) {
			if (typeof partiallyVisible === "undefined") partiallyVisible = false;
			//const { top, left, bottom, right } = el.getBoundingClientRect();
			var elRect = el.getBoundingClientRect();
			var top = elRect.top,
				left = elRect.left,
				bottom = elRect.bottom,
				right = elRect.right;
			var innerWidth = window.innerWidth,
				innerHeight = window.innerHeight;
			return partiallyVisible ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
		},

		// getScrollPosition: 返回当前页的滚动位置
		// 如果已定义, 则使用pageXOffset和pageYOffset, 否则scrollLeft和scrollTop。可以省略el以使用window的默认值
		getScrollPosition: function (elm) {
			var el = elm || window;
			var position = {
				x: (el.pageXOffset !== undefined) ? el.pageXOffset : el.scrollLeft,
				y: (el.pageYOffset !== undefined) ? el.pageYOffset : el.scrollTop
			};
			return position;
		},

		// getURLParameters: 返回一个包含当前 URL 参数的对象
		// eg: URL: https://example.com/?post=1234&action=edit
		// getURLParameters will return:
		// {
		//		post: "1234",
		// 		action: "edit"
		// }
		getURLParameters: function (url) {
			// Capture query string and initialize new object
			var me = this;
			var query = url || me.currentURL();
			var obj = {};
			// If no query string, return empty object
			if (query === '') return obj;
			if (!query.startsWith('?'))
				query = query.substring(query.indexOf('?'));
			// Remove the '?' at front of query string
			query = query.slice(1);
			// Split the query string into key/value pairs (ampersand-separated)
			query = query.split('&');
			// Loop through each key/value pair
			me.map(query, function (part) {
				var key;
				var value;
				// Split each key/value pair into their separate parts
				part = part.split('=');
				key = part[0];
				value = part[1];
				// If the key doesn't exist yet, set it
				if (!obj[key]) {
					obj[key] = value;
				} else {
					// If it does already exist...
					// If it's not an array, make it an array
					if (!me.isArray(obj[key])) {
						obj[key] = [obj[key]];
					}
					// Push the new value to the key's array
					obj[key].push(value);
				}
			});
			// Return the query string object
			return obj;
		},

		//父窗口链接重定向
		topRedirect: function (theLocation) {
			window.top.location.href = theLocation;
		},
		// redirect: 重定向到指定的 URL
		// 使用window.location.href或window.location.replace()重定向到url。传递第二个参数以模拟链接单击 (true-默认值) 或 HTTP 重定向 (false)
		redirect: function (url, asLink) {
			if (typeof asLink === undefined) asLink = true;
			return asLink ? window.location.href = url : window.location.replace(url);
		},

		// scrollToTop: 平滑滚动到页面顶部
		// 使用document.documentElement.scrollTop或document.body.scrollTop从顶部获取距离。从顶部的距离的一小部分滚动。使用window.requestAnimationFrame()对滚动进行动画处理
		scrolllToTop: function () {
			var c = document.documentElement.scrollTop || document.body.scrollTop;
			if (c > 0) {
				window.requestAnimationFrame(scrollToTop);
				window.scrollTo(0, c - c / 8);
			}
		},

		/**
		  * 获取浏览器内核
		  * @return Object
		  * {
		  *		"-khtml": false,
		  *		"-moz": false,
		  *		"-ms": fasle,
		  *		"-o": false,
		  *		"-webkit": true,
		  *		isMobile: false,
		  *		isNode: false,
		  *		isPC: true,
		  *		isLocalStorage: true,
		  *		isSessionStorage: true
		  *	 }
		  */
		browserInfo: function () {
			var staticStrUndefined = "undefined";
			var staticDocument = typeof document === staticStrUndefined ? 0 : document;
			var staticWindow = typeof window === staticStrUndefined ? 0 : window
			var assign = this.assign;
			var arrayEach = this.arrayEach;

			/* eslint-disable valid-typeof */
			function isBrowseStorage(storage) {
				try {
					var testKey = '__xe_t'
					storage.setItem(testKey, 1)
					storage.removeItem(testKey)
					return true
				} catch (e) {
					return false
				}
			}
			function isBrowseType(type) {
				return navigator.userAgent.indexOf(type) > -1
			}
			
			var $body, isChrome, isEdge
			var isMobile = false
			var result = {
				isNode: false,
				isMobile: isMobile,
				isPC: false,
				isDoc: !!staticDocument
			}
			if (!staticWindow && typeof process !== staticStrUndefined) {
				result.isNode = true
			} else {
				isEdge = isBrowseType('Edge')
				isChrome = isBrowseType('Chrome')
				isMobile = /(Android|webOS|iPhone|iPad|iPod|SymbianOS|BlackBerry|Windows Phone)/.test(navigator.userAgent)
				if (result.isDoc) {
					$body = staticDocument.body || staticDocument.documentElement
					arrayEach(['webkit', 'khtml', 'moz', 'ms', 'o'], function (core) {
						result['-' + core] = !!$body[core + 'MatchesSelector']
					})
				}
				assign(result, {
					edge: isEdge,
					firefox: isBrowseType('Firefox'),
					msie: !isEdge && result['-ms'],
					safari: !isChrome && !isEdge && isBrowseType('Safari'),
					isMobile: isMobile,
					isPC: !isMobile,
					isLocalStorage: isBrowseStorage(staticWindow.localStorage),
					isSessionStorage: isBrowseStorage(staticWindow.sessionStorage)
				})
			}
			return result
        },
		/************************************************************************
		 * 函数类
		************************************************************************/
		// await sleep(2000); For ES6 support
		// Usage!
		//  sleep(500).then(() => {
		//		// Do something after the sleep!
		//  });
		sleep: function (ms) {
			//return new Promise(resolve => setTimeout(resolve, ms));
			return new Promise(function (resolve) {
				setTimeout(resolve, ms);
			});
		},

		/************************************************************************
		* Math 数学类
		************************************************************************/

		// generateRandom: 产生两个参数之间的随机数
		generateRandom: function (lowerValue, upperValue) {
			var chioces = upperValue - lowerValue + 1;
			return Math.floor(Math.random() * chioces + lowerValue);
		},

		// isDivisible: 检查第一个数值参数是否可被另一个数字变量整除
		// 使用模数运算符 (%) 检查余数是否等于0
		isDivisible: function (dividend, divisor) {
			return dividend % divisor === 0;
		},

		// isEven: 如果给定的数字为偶数, 则返回true, 否则为false
		isEven: function (num) {
			return num % 2 === 0;
		},

		helperCreateToNumber: function (handle) {
			var me = this;
			return function (str) {
				if (str) {
					var num = handle(str);
					if (me.isNumber(num)) {
						return num;
					}
				}
				return 0;
			}
		},

		/**
		 * 将value转换为Number类型数值 浮点数
		 * 此函数会过滤字符串中的数字
		 * @param {any} value
		 * PMSUtils.toNumber(123) // 123
		 * PMSUtils.toNumber('12.3') // 12.3
		 * PMSUtils.toNumber('abc123+') // 123
		 * PMSUtils.toNumber('abc') // 0
		 */
		toNumber: function (value) {
			if (this.isNumber(value) && !this.isString(value)) return value;
			var numberStr = this.onlyNumber(value);
			var parser = this.helperCreateToNumber(parseFloat);
			return parser(numberStr);
		},

		/**
		 * 将value转换为Number类型数值 整数取整(四舍五入)
		 * 此函数会过滤字符串中的数字
		 * @param {any} value
		 * PMSUtils.toInteger(123) // 123
		 * PMSUtils.toInteger('12.34') // 12
		 * PMSUtils.toNumber('abc123+') // 123
		 * PMSUtils.toNumber(12.54) // 13
		 */
		toInteger: function (value) {
			var number = this.toNumber(value);
			return Math.round(number);
		},
		/************************************************************************
		 * 字符串类
		 ************************************************************************/
		// 清除字符串左侧或右侧的任意空格
		trim: function (str) {
			return str.replace(/^\s+|\s+$/g, "");
		},

		// 清除左空格
		ltrim: function (str) {
			return str.replace(/^\s+/, "");
		},

		// 清除右空格
		rtrim: function (val) {
			return val.replace(/\s+$/, "");
		},

		// 截取给定长度的字符串
		truncate: function (str, len) {
			if (str.length > len) {
				str = str.substring(0, len);
			}
			return str;
		},

		// 只返回字符串a-z字符
		onlyLetters: function (str) {
			return str.toLowerCase().replace(/[^a-z]/g, "");
		},

		// 只返回字符串中a-z和数字
		onlyLettersNums: function (str) {
			return str.toLowerCase().replace(/[^a-z,0-9]/g, "");
		},

		//过滤字符串中的数字
		onlyNumber: function (str) {
			var numberStr = "";
			if (this.isString(str)) {
				numberStr = str.replace(/[^0-9]/g, '');
			}
			return numberStr;
        },
		// capitalizeEveryWord: 将字符串中每个单词的首字母大写
		// 使用replace()匹配每个单词和toUpperCase()的第一个字符以将其大写
		capitalizeEveryWord: function (str) {
			//return str.replace(/\b[a-z]/g, char => char.toUpperCase());
			/*
			 * ^ matches the beginning of the string.
			 * \w matches any word character.
			 * {1} takes only the first character.
			 * Thus, ^\w{1} matches the first letter of the word.
			 * | works like the boolean OR. It matches the expression after and before the |.
			 * \s+ matches any amount of whitespace between the words (for example spaces, tabs, or line breaks).
			 */
			return str.replace(/(^\w{1})|(\s+\w{1})/g, function (letter) {
				return letter.toUpperCase();
			});
		},

		// fromCamelCase: 从驼峰表示法转换为字符串形式
		// 使用replace()可删除下划线、连字符和空格, 并将单词转换为匹配。省略第二个参数以使用默认分隔符_
		fromCamelCase: function (str, separator) {
			if (typeof separator === "undefined") separator = "_";
			return str.replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2').replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2').toLowerCase();
		},

		// toCamelCase: 字符串转换为驼峰模式
		// 使用replace()可删除下划线、连字符和空格, 并将单词转换为驼峰模式
		toCamelCase: function (str) {
			return str.replace(/^([A-Z])|[\s-_]+(\w)/g, function (match, p1, p2, offset) {
				return p2 ? p2.toUpperCase() : p1.toLowerCase();
			});
		},

		// truncateString: 将字符串截断为指定长度
		// 确定字符串的length是否大于num。将截断的字符串返回到所需的长度, 并将...追加到末尾或原始字符串
		truncateString: function (str, num) {
			return str.length > num ? str.slice(0, num > 3 ? num - 3 : num) + "..." : str;
		},

		parseToIntegerArray: function (str, sep) {
			sep = sep || ',';
			if (!this.isString(str) || str.length <= 0) return [];
			const me = this;
			var intArray = me.map(str.split(sep), function (item) {
				return me.toNumber(item);
			});
			return intArray;
        },
		/************************************************************************
		 * Object
		 ************************************************************************/
		/**
		 * 判断对象自身属性中是否具有指定的属性
		 * @param {Object} obj 对象
		 * @param {String/Number} key 键值
		 * @return {Boolean}
		 **/
		hasOwnProp: function (obj, key) {
			return obj && obj.hasOwnProperty ? obj.hasOwnProperty(key) : false;
		},

		/**
		 * 返回一个获取对象属性的函数
		 * @param {String} name 属性名
		 * @param {Object} defs 空值
		 * */
		property: function (name, defs) {
			return function (obj) {
				return obj === null ? defs : obj[name];
			}
		},

		/**
		  * 指定方法后的返回值组成的新对象
		  *
		  * @param {Object} obj 对象/数组
		  * @param {Function} iterate(item, index, obj) 回调
		  * @param {Object} context 上下文
		  * @return {Object}
		  */
		objectMap: function (obj, iterate, context) {
			var result = {};
			if (obj) {
				if (iterate) {
					if (!this.isFunction(iterate)) {
						iterate = this.property(iterate);
					}
					this.each(obj, function (val, index) {
						result[index] = iterate.call(context, val, index, obj);
					})
				} else {
					return obj;
				}
			}
			return result;
		},

		/**
		* 将一个或多个源对象复制到目标对象中
		* @param {Object} target 目标对象
		* @param {...Object}
		* @return {Boolean}
		**/
		assign: function (target) {
			var objectAssignFns = Object.assign;
			var me = this;

			var handleAssign = function (destination, args, isClone) {
				var len = args.length;
				var keys = me.helperCreateGetObjects('keys', 1)
				for (var source, index = 1; index < len; index++) {
					source = args[index]
					me.arrayEach(keys(args[index]), isClone ? function (key) {
						destination[key] = me.clone(source[key], isClone);
					} : function (key) {
						destination[key] = source[key];
					})
				}
				return destination;
			};
			if (target) {
				var args = arguments
				if (target === true) {
					if (args.length > 1) {
						target = me.isArray(target[1]) ? [] : {};
						return handleAssign(target, args, true);
					}
				} else {
					return objectAssignFns ? objectAssignFns.apply(Object, args) : handleAssign(target, args)
				}
			}
			return target;
		},

		/**
		 * 获取对象的属性的值，如果值为 undefined，则返回默认值
		 * @param {Object/Array} obj 对象
		 * @param {String/Function} property 键、路径
		 * @param {Object} defaultValue 默认值
		 * @return {Object}
		 *  PMSUtils.get({a: {b: 11, c: 22, d: [33, 44]}}, 'a.b') // 11
		 *  PMSUtils.get({a: {b: 11, c: 22, d: [33, 44]}}, 'a.e', 'default') // 'default'
		 *  PMSUtils.get({a: {b: 11, c: 22, d: [33, 44]}}, 'a.d[0]') // 33
		 *  PMSUtils.get({a: {b: 11, c: 22, d: [33, {f: 66}]}}, 'a.d[1].f') // 66
		 */
		get: function (obj, property, defaultValue) {
			var staticHGKeyRE = /(.+)?\[(\d+)\]$/
			var me = this;
			var helperGetHGSKeys = function (property) {
				// 以最快的方式判断数组，可忽略准确性
				return property ? (property.splice && property.join ? property : ('' + property).split('.')) : [];
			};
			var valGet = function (obj, key) {
				var matchs = key ? key.match(staticHGKeyRE) : '';
				return matchs ? (matchs[1] ? (obj[matchs[1]] ? obj[matchs[1]][matchs[2]] : undefined) : obj[matchs[2]]) : obj[key];
			};
			var pathGet = function(obj, property) {
				if (obj) {
					var rest, props, len;
					var index = 0;
					if (obj[property] || me.hasOwnProp(obj, property)) {
						return obj[property];
					} else {
						props = helperGetHGSKeys(property);
						len = props.length;
						if (len) {
							for (rest = obj; index < len; index++) {
								rest = valGet(rest, props[index]);
								if (null === rest || undefined === rest) {
									if (index === len - 1) {
										return rest;
									}
									return;
								}
							}
						}
						return rest;
					}
				}
			}
			if (obj === null || obj === undefined) {
				return defaultValue;
			}
			var result = pathGet(obj, property);
			return result === undefined ? defaultValue : result;			
		},

		/**
		 * 设置对象属性上的值。如果属性不存在则创建它
		 * @param {Object/Array} obj 对象
		 * @param {String/Function} property 键、路径
		 * @param {Object} value 值
		 * PMSUtils.set({}, 'a.d[0]', 33) // {a: {d: [33]}}
		 * PMSUtils.set({a: {}}, 'a.d[0].f.h', 44) // {a: {d: [{f: {h: 44}}]}}
		 * PMSUtils.set({}, ['a', 'c'], 22) // {a: {c: 22}}
		 * PMSUtils.set({}, ['a', 'd[0]', 'f', 'h'], 44) // {a: {d: [{f: {h: 44}}]}}
		 */
		set: function (obj, property, value)  {
			var helperGetHGSKeys = function (property) {
				// 以最快的方式判断数组，可忽略准确性
				return property ? (property.splice && property.join ? property : ('' + property).split('.')) : [];
			};
			var sKeyRE = /(.+)\[(\d+)\]$/
			var valSet = function (obj, key, isSet, value) {
				if (obj[key]) {
					if (isSet) {
						obj[key] = value;
					}
				} else {
					var index;
					var matchs = key ? key.match(sKeyRE) : null;
					var rest = isSet ? value : {};
					if (matchs) {
						index = parseInt(matchs[2]);
						if (obj[matchs[1]]) {
							obj[matchs[1]][index] = rest;
						} else {
							obj[matchs[1]] = new Array(index + 1);
							obj[matchs[1]][index] = rest;
						}
					} else {
						obj[key] = rest;
					}
					return rest;
				}
				return obj[key];
			};
			
			if (obj) {
				if (obj[property] || this.hasOwnProp(obj, property)) {
					obj[property] = value;
				} else {
					var rest = obj;
					var props = helperGetHGSKeys(property);
					var len = props.length;
					for (var index = 0; index < len; index++) {
						rest = valSet(rest, props[index], index === len - 1, value);
					}
				}
			}
			return obj;
		},

		/**
		 * 检查键、路径是否是该对象的属性
		 *
		 * @param {Object/Array} data 对象
		 * @param {String/Function} property 键、路径
		 * @return {Boolean}
		 * PMSUtils.has({a: {b: 11, c: 22, d: [33, 44]}}, 'a.b') // true
		 * PMSUtils.has({a: {b: 11, c: 22, d: [33, 44]}}, 'a.e') // false
		 * PMSUtils.has({a: {b: 11, c: 22, d: [33, 44]}}, 'a.d[0]') // true
		 * PMSUtils.has({a: {b: 11, c: 22, d: [33, {f: 66}]}}, 'a.d[1].f') // true
		 * PMSUtils.has({a: {b: 11, c: 22, d: [33, 44]}}, ['a', 'd[1]']) // true
		 * PMSUtils.has({a: {b: 11, c: 22, d: [33, 44]}}, ['a', 'd[3]']) // false
		 */
		has: function (obj, property) {
			var staticHGKeyRE = /(.+)?\[(\d+)\]$/
			var helperGetHGSKeys = function (property) {
				// 以最快的方式判断数组，可忽略准确性
				return property ? (property.splice && property.join ? property : ('' + property).split('.')) : [];
			};
			
			if (obj) {
				if (this.hasOwnProp(obj, property)) {
					return true;
				} else {
					var prop, arrIndex, objProp, matchs, rest, isHas;
					var props = helperGetHGSKeys(property);
					var index = 0;
					var len = props.length;
					for (rest = obj; index < len; index++) {
						isHas = false;
						prop = props[index];
						matchs = prop ? prop.match(staticHGKeyRE) : '';
						if (matchs) {
							arrIndex = matchs[1];
							objProp = matchs[2];
							if (arrIndex) {
								if (rest[arrIndex]) {
									if (this.hasOwnProp(rest[arrIndex], objProp)) {
										isHas = true;
										rest = rest[arrIndex][objProp];
									}
								}
							} else {
								if (this.hasOwnProp(rest, objProp)) {
									isHas = true;
									rest = rest[objProp];
								}
							}
						} else {
							if (this.hasOwnProp(rest, prop)) {
								isHas = true;
								rest = rest[prop];
							}
						}
						if (isHas) {
							if (index === len - 1) {
								return true;
							}
						} else {
							break;
						}
					}
				}
			}
			return false
		},

		/**
		  * 清空对象
		  *
		  * @param {Object} obj 对象
		  * @param {*} defs 默认值,如果不传（清空所有属性）、如果传对象（清空并继承)、如果传值(给所有赋值)
		  * @param {Object/Array} assigns 默认值
		  * @return {Object}
		  * 
		  * let a = [11, 22, 33, 33]
		  * PMSUtils.clear(a) // []
		  * PMSUtils.clear(a, undefined) // [undefined, undefined, undefined, undefined]
		  * PMSUtils.clear(a, null) // [null, null, null, null]
		  * let b = {b1: 11, b2: 22}
		  * PMSUtils.clear(b) // {}
		  * PMSUtils.clear(b, undefined) // {b1: undefined, b2: undefined}
		  * PMSUtils.clear(b, null) // {b1: null, b2: null}
		  * */
		clear: function (obj, defs, assigns) {
			var helperDeleteProperty = function (obj, property) {
				try {
					delete obj[property];
				} catch (e) {
					obj[property] = undefined
				}
			};
			if (obj) {
				var len;
				var isDefs = arguments.length > 1 && (defs === null || !this.isObject(defs));
				var extds = isDefs ? assigns : defs;
				if (this.isPlainObject(obj)) {
					this.objectEach(obj, isDefs ? function (val, key) {
						obj[key] = defs;
					} : function (val, key) {
						helperDeleteProperty(obj, key)
					});
					if (extds) {
						this.assign(obj, extds);
					}
				} else if (this.isArray(obj)) {
					if (isDefs) {
						len = obj.length;
						while (len > 0) {
							len--;
							obj[len] = defs;
						}
					} else {
						obj.length = 0;
					}
					if (extds) {
						obj.push.apply(obj, extds);
					}
				}
			}
			return obj;
		},

		/**
		 * 移除对象属性
		 * @param {Object/Array} obj 对象/数组
		 * @param {Function/String} iterate 方法或属性
		 * @param {Object} context 上下文
		 * @return {Object/Array}
		 * let list1 = [11, 22, 33, 44]
		 * PMSUtils.remove(list1, 2) // list1 = [11, 22, 44]
		 * let list2 = [11, 22, 33, 44]
		 * PMSUtils.remove(list2, item => item === 22) // list2 = [11, 33, 44]
		 * */
		remove: function (obj, iterate, context) {
			var helperDeleteProperty = function (obj, property) {
				try {
					delete obj[property];
				} catch (e) {
					obj[property] = undefined
				}
			};

			var pluckProperty = function (name) {
				return function (obj, key) {
					return key === name;
				}
			};
			var eqNull = function (obj) {
				return obj === null || obj === undefined;
			};

			if (obj) {
				if (!eqNull(iterate)) {
					var removeKeys = [];
					var rest = [];
					if (!this.isFunction(iterate)) {
						iterate = pluckProperty(iterate)
					}
					this.each(obj, function (item, index, rest) {
						if (iterate.call(context, item, index, rest)) {
							removeKeys.push(index);
						}
					});
					if (this.isArray(obj)) {
						this.lastEach(removeKeys, function (item, key) {
							rest.push(obj[item]);
							obj.splice(item, 1);
						});
					} else {
						rest = {};
						this.arrayEach(removeKeys, function (key) {
							rest[key] = obj[key];
							helperDeleteProperty(obj, key);
						});
					}
					return rest;
				}
				return this.clear(obj);
			}
			return obj;
        },
		/************************************************************************
		* 其他
		************************************************************************/
		// gettype: 返回值的本机类型
		// 如果值未定义或为 null, 则返回小写的构造函数名称、"未定义" 或 "null"
		gettype: function (v) {
			return v === undefined ? "undefined" : v === null ? "null" : v.constructor.name.toLowerCase();
		},

		/**
		 * arrayEach (obj, iteratee [, context]) 数组迭代器
		 * PMSUtils.arrayEach([11, 22, 33], (item, key) => {
		 *	// 数组迭代器，只能用于遍历(数组或伪数组)，性能高于 each
		 * });
		**/
		arrayEach: function (list, iterate, context) {
			if (list) {
				if (list.forEach) {
					list.forEach(iterate, context);
				} else {
					for (var index = 0, len = list.length; index < len; index++) {
						iterate.call(context, list[index], index, list);
					}
				}
			}
		},

		/**
		 * objectEach (obj, iteratee [, context]) 对象迭代器
		 * @param {any} obj
		 * @param {any} iterate
		 * @param {any} context
		 * PMSUtils.objectEach([11, 22, 33], (item, key) => {
		 *	// 对象迭代器，只能用于遍历对象，性能高于 each
		 * });
		 */
		objectEach: function (obj, iterate, context) {
			//DavidLee : Confused a little bit!!
			//this refers to window object not PMSUtils....
			// still dont know why
			//var me = this;  
			
			if (obj) {
				for (var key in obj) {
					//if (me.hasOwnProp(obj, key)) {
					if (obj && (obj.hasOwnProperty ? obj.hasOwnProperty(key) : false)) {
						iterate.call(context, obj[key], key, obj);
					}
				}
			}
		},
		/**
		* 迭代器
		* @param {Object} obj 对象/数组
		* @param {Function} iterate(item, index, obj) 回调
		* @param {Object} context 上下文
		* @return {Object}
	    * PMSUtils.each([11, 22, 33], (item, key) => {
	    *	// 通用迭代器，支持遍历任意类型
	    * })
		*/
		each: function (obj, iterate, context) {
			if (obj) {
				return (this.isArray(obj) ? this.arrayEach : this.objectEach)(obj, iterate, context);
			}
			return obj;
		},

		lastArrayEach: function (obj, iterate, context) {
			for (var len = obj.length - 1; len >= 0; len--) {
				iterate.call(context, obj[len], len, obj)
			}
		},

		lastObjectEach: function (obj, iterate, context) {
			var keys = this.helperCreateGetObjects('keys', 1);
			this.lastArrayEach(keys(obj), function (key) {
				iterate.call(context, obj[key], key, obj);
			});
        },
		/**
		 * 迭代器,从最后开始迭代
		 * @param {Object} obj 对象/数组
		 * @param {Function} iterate(item, index, obj) 回调
		 * @param {Object} context 上下文
		 * @return {Object}
		 * PMSUtils.lastEach([11, 22, 33], (item, key) => {
	     *	// 通用迭代器，支持遍历任意类型
	     * })
		 * */
		lastEach: function (obj, iterate, context) {
			var lastArrayEach = this.lastArrayEach;
			var lastObjectEach = this.lastObjectEach;

			if (obj) {
				return (this.isArray(obj) ? lastArrayEach : lastObjectEach)(obj, iterate, context);
			}
			return obj;
		},

		helperCreateGetObjects: function (name, getIndex) {
			var proMethod = Object[name];
			var me = this;
			return function (obj) {
				var result = [];
				if (obj) {
					if (proMethod) {
						return proMethod(obj);
					}
					me.each(obj, getIndex > 1
						? function (key) {
							result.push(['' + key, obj[key]]);
						}
						: function () {
							result.push(arguments[getIndex]);
						}
					);
				}
				return result;
			};
		},

		/**
		 * 指定方法后的返回值组成的新数组
		 * @param {Object} obj 对象/数组
		 * @param {Function} iterate(item, index, obj) 回调
		 * @param {Object} context 上下文
		 * @return {Array}
		 * */
		map: function (obj, iterate, context) {
			var result = [];
			if (obj && arguments.length > 1) {
				if (obj.map) {
					return obj.map(iterate, context);
				} else {
					this.each(obj, function () {
						result.push(iterate.apply(context, arguments))
					});
				}
			}
			return result;
		},
		
		/**
		* 浅拷贝/深拷贝
		*
		* @param {Object} obj 对象/数组
		* @param {Boolean} deep 是否深拷贝
		* @return {Object}
		*/
		clone: function (obj, deep) {
			var objectToString = Object.prototype.toString;
			var isPlainObject = this.isPlainObject;
			var objectMap = this.objectMap;
			var map = this.map;
			var isArray = this.isArray;

			var copyValue = function (val, deep) {
				if (isPlainObject(val)) {
					return handleObjectAndArrayClone(objectMap, val, deep);
				} else if (isArray(val)) {
					return handleObjectAndArrayClone(map, val, deep);
				}
				return handleValueClone(val, deep);
			};
			var handleObjectAndArrayClone = function (func, obj, deep) {
				return func(obj, deep ? function (val) {
					return copyValue(val, deep);
				} : function (val) {
					return val;
				});
			};
			var handleValueClone = function (val, deep) {
				if (deep && val) {
					var Ctor = val.constructor;
					switch (objectToString.call(val)) {
						case "[object Date]":
						case "[object RegExp]":
							return new Ctor(val.valueOf());
						case "[object Set]":
							var set = new Ctor();
							val.forEach(function (v) {
								set.add(v);
							})
							return set
						case "[object Map]":
							var map = new Ctor();
							val.forEach(function (v, k) {
								map.set(k, v);
							})
							return map;
					}
				}
				return val;
			};
		
			if (obj) {
				return copyValue(obj, deep);
			}
			return obj;
        },
	
		/**
		 * 将对象或者伪数组转为新数组
		 * @param {Array} list 数组
		 * @return {Array}
		 * */
		toArray: function (list) {
			return this.map(list, function (item) {
				return item;
			});
		},
		/**
		 * 将数组进行排序
		 * @param {Array} arr 数组
		 * @param {Function/String/Array} fieldConfs 方法或属性
		 * @param {Object} context 上下文
		 * @return {Array}
		 * 
		 * // 数值排序
		 * PMSUtils.orderBy([11, 55, 99, 77, 11, 55, 22])
		 * //result: [11, 11, 22, 55, 55, 77, 99]
		 * // 字母排序
		 * PMSUtils.orderBy(['x', 'z', 'g', 'q', 'e', 'b', 'a', 'g', 'f', 'c', 'j'])
		 * //result: ["a", "b", "c", "e", "f", "g", "g", "j", "q", "x", "z"]
		 * // 中文排序
		 * PMSUtils.orderBy(['小', '何', '李', '林', '有', '好', '啊', '的', '出', '库', '徐'])
		 * //result: ['啊', '出', '的', '好', '何', '库', '李', '林', '小', '徐', '有']
		 * // 倒序
		 * PMSUtils.orderBy([11, 55, 99, 77, 11, 55, 22], { order: 'desc' })
		 * //result: [99, 77, 55, 55, 22, 11, 11]
		 * // 深层对象
		 * PMSUtils.orderBy([{ age: 27 }, { age: 26 }, { age: 28 }], 'age')
		 * PMSUtils.orderBy([{ age: 27 }, { age: 26 }, { age: 28 }], ['age', 'desc'])
		 * //result: [{ age: 26 }, { age: 27 }, { age: 28 }]
		 * // 多字段排序
		 * PMSUtils.orderBy([
		 *	{ name: 'x', age: 26 },
		 *	{ name: 'd', age: 27 },
		 *	{ name: 'z', age: 26 },
		 *	{ name: 'z', age: 24 },
		 *	{ name: 'z', age: 25 }
		 * ], [['age', 'asc'], ['name', 'desc']])
		 * // result:
		 *  [
		 *   { name: 'z', age: 24 },
		 *   { name: 'z', age: 25 },
		 *   { name: 'x', age: 26 },
		 *   { name: 'z', age: 26 },
		 *   { name: 'd', age: 27 }
		 *  ]
		 *  // 自定义组合排序
		 *  PMSUtils.orderBy([
		 *		{ name: 'x', age: 26 },
		 *		{ name: 'd', age: 27 },
		 *		{ name: 'x', age: 26 },
		 *		{ name: 'z', age: 26 }
		 *	], [[item => item.name, 'asc'], [field: item => item.age, 'desc']])
		 *  //result:         
		 *  [{ name: 'd', age: 27 },
		 *   { name: 'x', age: 26 },
		 *   { name: 'x', age: 26 },
		 *   { name: 'z', age: 26 }]
		 **/
		orderBy: function (arr, fieldConfs, context) {
			var ORDER_PROP_ASC = 'asc';
			var ORDER_PROP_DESC = 'desc';
			var me = this;
			// '' < 数字 < 字符 < null < undefined
			var handleSort = function (v1, v2) {
				if (v1 === undefined) {
					return 1;
				}
				if (v1 === null) {
					return v2 === undefined ? -1 : 1;
				}
				return v1 && v1.localeCompare ? v1.localeCompare(v2) : (v1 > v2 ? 1 : -1);
			};
			var buildMultiOrders = function (name, confs, compares) {
				return function (item1, item2) {
					var v1 = item1[name];
					var v2 = item2[name];
					if (v1 === v2) {
						return compares ? compares(item1, item2) : 0;
					}
					return confs.order === ORDER_PROP_DESC ? handleSort(v2, v1) : handleSort(v1, v2);
				};
			};
			var getSortConfs = function (arr, list, fieldConfs, context) {
				var sortConfs = [];
				fieldConfs = me.isArray(fieldConfs) ? fieldConfs : [fieldConfs];
				me.arrayEach(fieldConfs, function (handle, index) {
					if (handle) {
						var field = handle;
						var order;
						if (me.isArray(handle)) {
							field = handle[0];
							order = handle[1];
						} else if (me.isPlainObject(handle)) {
							field = handle.field;
							order = handle.order;
						}
						sortConfs.push({
							field: field,
							order: order || ORDER_PROP_ASC
						});
						me.arrayEach(list, me.isFunction(field) ? function (item, key) {
							item[index] = field.call(context, item.data, key, arr);
						} : function (item) {
							item[index] = field ? me.get(item.data, field) : item.data;
						});
					}
				});
				return sortConfs;
			};
			if (arr) {
				if (fieldConfs === null || fieldConfs === undefined) {
					return this.toArray(arr).sort(handleSort);
				}
				var compares;
				var list = this.map(arr, function (item) {
					return { data: item };
				});
				var sortConfs = getSortConfs(arr, list, fieldConfs, context);
				var len = sortConfs.length - 1;
				while (len >= 0) {
					compares = buildMultiOrders(len, sortConfs[len], compares);
					len--;
				}
				if (compares) {
					list = list.sort(compares);
				}
				return this.map(list, this.property('data'));
			}
			return [];
		},

		/**
		 * 将一个树结构转成数组列表
		 * @param {Array} array 数组
		 * @param {Object} options { children: 'children', data: 'data', clear: false }
		 * @return {Array}
		 * */
		toTreeArray: function (array, options) {
			var treeDefaultOpts = {
				children: 'children',
				data: 'data',
				clear: false
			};
			var me = this;
			var unTreeList = function (result, array, opts) {
				var optChildren = opts.children;
				var optData = opts.data;
				var optClear = opts.clear;
				me.each(array, function (item) {
					var children = item[optChildren];
					if (optData) {
						item = item[optData];
					}
					result.push(item);
					if (children && children.length) {
						unTreeList(result, children, opts);
					}
					if (optClear) {
						delete item[optChildren];
					}
				});
				return result;
			};
			return unTreeList([], array, me.assign({}, treeDefaultOpts, options));
		},

		/**
		 * 将一个带层级的数据列表转成树结构
		 * @param {Array} array 数组
		 * @param {Object} options {strict: false, parentKey: 'parentId', key: 'id', children: 'children', data: 'data'}
		 * @return {Array}
		 * 
		 * options: 
		 * 属性(key)  ||   描述     	                       |  默认值
		 * strict     |	是否严格模式，会去掉父子关联不存在数据，  |  false
		 *            | 当子节点为空时将没有children 属性      |
		 * key		  |	节点键值	                           |  id
		 * parentKey  |	父节点键值	                       | parentId
		 * children	  | 数据存放属性						   | children
		 * sortKey	  |	对树节点进行排序属性				   |  
		 * reverse	  |	sortKey不为空是有效，默认升序		   | false
		 * data		  | 数据存放属性	                       | null
		 * */
		toArrayTree: function (array, options) {
			var treeDefaultOpts = {
				strict: false,
				parentKey: 'parentId',
				key: 'id',
				children: 'children',
				data: null,
				sortKey: '',
				reverse: false
			};
			var me = this;
			var helperCreateIndexOf = function (name, callback) {
				return function (obj, val) {
					if (obj) {
						if (typeof obj === 'string' || me.isArray(obj)) {
							if (obj[name]) {
								return obj[name](val)
							}
							return callback(obj, val)
						}
						for (var key in obj) {
							if (me.hasOwnProp(obj, key)) {
								if (val === obj[key]) {
									return key
								}
							}
						}
					}
					return -1
				};
            }
			var arrayIndexOf = function (list, val) {
				if (list.indexOf) {
					return list.indexOf(val)
				}
				for (var index = 0, len = list.length; index < len; index++) {
					if (val === list[index]) {
						return index
					}
				}
			};
			var includes = function (obj, val) {
				var indexOf = helperCreateIndexOf('indexOf', arrayIndexOf)
				return indexOf(obj, val) !== -1
			};
			var strictTree = function (array, optChildren) {
				me.each(array, function (item) {
					if (item.children && !item.children.length) {
						me.remove(item, optChildren);
					}
				});
			};
			var opts = me.assign({}, treeDefaultOpts, options);
			var optStrict = opts.strict;
			var optKey = opts.key;
			var optParentKey = opts.parentKey;
			var optChildren = opts.children;
			var optSortKey = opts.sortKey;
			var optReverse = opts.reverse;
			var optData = opts.data;
			var result = [];
			var treeMap = {};
			var idList, id, treeData, parentId;

			if (optSortKey && optSortKey.length > 0) {
				array = me.orderBy(me.clone(array), optSortKey);
				if (optReverse) {
					array = array.reverse();
				}
			}
			idList = me.map(array, function (item) {
				return item[optKey];
			});

			me.each(array, function (item) {
				id = item[optKey];
				if (optData) {
					treeData = {};
					treeData[optData] = item;
				} else {
					treeData = item;
				}
				parentId = item[optParentKey];
				treeMap[id] = treeMap[id] || [];
				treeMap[parentId] = treeMap[parentId] || [];
				treeMap[parentId].push(treeData);
				treeData[optKey] = id;
				treeData[optParentKey] = parentId;
				treeData[optChildren] = treeMap[id];

				if (!optStrict || (optStrict && !parentId)) {
					if (!includes(idList, parentId)) {
						result.push(treeData);
					}
				}
			});
			if (optStrict) {
				strictTree(array, optChildren)
			}
			return result;
		},

	};

	return util;
})(window.PMSUtils || {});